package funtions;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.HashMap;
import java.util.regex.Pattern;

public class NGDFitnessFunction {

	/**
	 * This attribute must be initialize
	 */
	private double totalNumSent;
	private NGDSentenceInfoStructure[] ngdInfoStructure;
	private HashMap<String, Float> termMap;
	private HashMap<String, Float> pairsTermMap;
	private int maxExecLength;
	private int maxLength;
	/**
	 * Attributes used in the method:
	 * 	public double calculateFitFunct(int[] individual)
	 */
	private int posSentence_I,posSentence_II;
	private int posInvidual;
	/**
	 * Attributes used in the method:
	 * 	private double calculateSimilarity(int sentPos_I,int sentPos_II)
	 */
	private String[] termText_Sent_I,termText_Sent_II;
	private int cs_i,cs_j;
	private double numerator,denominator,similarityResult;
	/**
	 * Attributes used in the method:
	 * 	private double calculateTotalLengthInd(int [] individual)
	 */
	private int indPos;
	/**
	 * Attributes used in the method:
	 * 	Fitness()
	 */
	public String [] sentLengthSet;
	/**
	 * Requires the set of sentences in order to be able to calculate the fitness function
	 */
	public  NGDFitnessFunction() {
		super();
		this.maxLength=250;
		this.maxExecLength=this.maxLength+(this.maxLength*20/100);
	}
	/**
	 * Load all the necessary info in memory to increase performance
	 * @throws FileNotFoundException 
	 */
	private Pattern pTermInstanceSplit=Pattern.compile("\n");
	private Pattern pTermProperties=Pattern.compile("/");
	private Pattern pSentFieldSplit=Pattern.compile("\t");

	public void init(String folderURL) throws FileNotFoundException,IOException{
		int inBufCap=320*1024*1024;//size in bytes
		ByteBuffer inBuf = ByteBuffer.allocateDirect(inBufCap);
		//Auxiliary Buffer to store the valid information
        ByteBuffer auxBuf=ByteBuffer.allocateDirect(inBufCap);
        
        //Read the terms array of the sentences
        File sentTermFile=new File(folderURL+"\\gnd_sent-term.txt");
        FileInputStream is=new FileInputStream(sentTermFile);
        FileChannel f=is.getChannel();
        
        float fsize=0;
        fsize=sentTermFile.length();
        String auxString_I="";
        auxString_I=getValidInformationFromBuff(f, inBufCap, inBuf, auxBuf, fsize);
        f.close();
        is.close();
        String[] infoPerSent;
        infoPerSent=this.pTermInstanceSplit.split(auxString_I);
        
      //Read the sentences lengths
        File sentLenFile=new File(folderURL+"\\sentencesLength.txt");
        FileInputStream isL=new FileInputStream(sentLenFile);
        FileChannel fL=isL.getChannel();
        fsize=sentLenFile.length();
        
        String auxString_II="";
        auxString_II=getValidInformationFromBuff(fL, inBufCap, inBuf, auxBuf, fsize);
        fL.close();
        isL.close();
        String[] infoLenSent=this.pTermInstanceSplit.split(auxString_II);
        
        System.out.println("Leyendo terminos y longitud de oraciones");
        NGDSentenceInfoStructure ndgStructure=new NGDSentenceInfoStructure();
        this.ngdInfoStructure=new NGDSentenceInfoStructure[infoPerSent.length];
        
        //Parameter for the fitness function
        this.sentLengthSet=new String[infoPerSent.length];
        //
        for(int w=0;w<infoPerSent.length;w++){
        	ndgStructure.setLength(Integer.parseInt(this.pSentFieldSplit.split(infoLenSent[w])[1]));
        	ndgStructure.setTerms(this.pTermProperties.split(infoPerSent[w]));
        	this.ngdInfoStructure[w]=(NGDSentenceInfoStructure)ndgStructure.clone();
        	this.sentLengthSet[w]=infoLenSent[w];
        }
        System.out.println("Hecho");
        
        //________
        this.totalNumSent=infoPerSent.length;
        //_______
        //________________________________
        
        //Read the terms array of the sentences
        sentTermFile=new File(folderURL+"\\gnd_terms.txt");
        is=new FileInputStream(sentTermFile);
        f=is.getChannel();
        
        fsize=0;
        fsize=sentTermFile.length();
        auxString_I="";
        auxString_I=getValidInformationFromBuff(f, inBufCap, inBuf, auxBuf, fsize);
        f.close();
        is.close();
        infoPerSent=this.pTermInstanceSplit.split(auxString_I);
        String [] fields=new String[2];
        System.out.println("Creando MAP de terminos");
        this.termMap=new HashMap<String, Float>(infoPerSent.length);
        for(int w=0;w<infoPerSent.length;w++){
        	fields=this.pSentFieldSplit.split(infoPerSent[w]);
        	this.termMap.put(fields[0], Float.parseFloat(fields[1]));
        }
        System.out.println("Hecho");
        
      //Read the terms PAIRS array of the sentences
        sentTermFile=new File(folderURL+"\\gnd_pairs.txt");
        is=new FileInputStream(sentTermFile);
        f=is.getChannel();
        
        fsize=0;
        fsize=sentTermFile.length();
        auxString_I="";
        System.out.println("Leyendo MAp de parejas");
        auxString_I=getValidInformationFromBuff(f, inBufCap, inBuf, auxBuf, fsize);
        f.close();
        is.close();
        infoPerSent=this.pTermInstanceSplit.split(auxString_I);
        fields=new String[2];
        this.pairsTermMap=new HashMap<String, Float>(infoPerSent.length);
        for(int w=0;w<infoPerSent.length;w++){
        	fields=this.pSentFieldSplit.split(infoPerSent[w]);
        	this.pairsTermMap.put(fields[0], Float.parseFloat(fields[1]));
        }
        System.out.println("Hecho");
        
        //Calculate the NGD values an put in the pairsTermMap
        Object[] termObject=this.termMap.keySet().toArray();
        System.out.println("Actualizando Map de terminos con NGD");
        for(int w=0;w<termObject.length;w++){
        	if(w+1<termObject.length)
        		calculateNGDTerms(termObject[w].toString(), termObject[w+1].toString());
        }
        System.out.println("Hecho");
	}
	public void initCoverage(){
		NGDSentenceInfoStructure infoStructure;
		double sentenceCoverage;
		for(int w=0;w<this.ngdInfoStructure.length;w++){
			infoStructure=(NGDSentenceInfoStructure)this.ngdInfoStructure[w].clone();
			sentenceCoverage=calculateCoverage(w);
			infoStructure.setCoverage(sentenceCoverage);
			this.ngdInfoStructure[w]=(NGDSentenceInfoStructure)infoStructure.clone();
		}
	}
	/**
	 * This method receives an individual whose length is |Sentences|. Is a boolean vector where 1 represents TRUE and 0 FALSE
	 * This function takes into account the restriction of maxLength. Where the max-length for execution purposes is a 20% larger
	 */
	public double calculateFitFunct(int[] individual){
		double fitnessFunctValue=0;
		double coverageValue_I=0,coverageValue_II=0;
		double redundancyValue=0;
		this.posSentence_I=0;
		this.posSentence_II=0;
		for(this.posInvidual=0;this.posInvidual<individual.length;this.posInvidual++){
			if(individual[this.posInvidual]==1&&this.posSentence_I==0){
				this.posSentence_I=this.posInvidual;
			}
			if(this.posSentence_I>0&&individual[this.posInvidual]==1&&this.posInvidual>this.posSentence_I){
				this.posSentence_II=this.posInvidual;
				//Calculate the Coverage and Redundancy function for the pair of sentences
				coverageValue_I=this.ngdInfoStructure[this.posSentence_I].getCoverage();
				coverageValue_II=this.ngdInfoStructure[this.posSentence_II].getCoverage();
				redundancyValue=calculateRedundancy(this.posSentence_I, this.posSentence_II);
				if(Double.isNaN(fitnessFunctValue))
					System.err.println("NaN value: \n Class NGDFitnessFunction\n Line 202"+"The value for the current situation are:\n coverageValue_I|coverageValue_II|redundancyValue"+
							coverageValue_I+"|"+coverageValue_II+"|"+redundancyValue);
				fitnessFunctValue=fitnessFunctValue+(coverageValue_I+coverageValue_II-redundancyValue);
				//
				this.posSentence_I=this.posSentence_II;
			}
		}
		//Check the individual length
		if(calculateTotalLengthInd(individual)>this.maxExecLength)
			fitnessFunctValue=0;
		return fitnessFunctValue;
	}
	/**
	 * This method receives an individual whose length is |Sentences|. Is a boolean vector where 1 represents TRUE and 0 FALSE
	 * This function *DONT* take into account the restriction of maxLength.
	 */
	public double calculateUnConstFitFunct(int[] individual){
		double fitnessFunctValue=0;
		double coverageValue_I=0,coverageValue_II=0;
		double redundancyValue=0;
		this.posSentence_I=0;
		this.posSentence_II=0;
		for(this.posInvidual=0;this.posInvidual<individual.length;this.posInvidual++){
			if(individual[this.posInvidual]==1&&this.posSentence_I==0){
				this.posSentence_I=this.posInvidual;
			}
			if(this.posSentence_I>0&&individual[this.posInvidual]==1&&this.posInvidual>this.posSentence_I){
				this.posSentence_II=this.posInvidual;
				//Calculate the Coverage and Redundancy function for the pair of sentences
				coverageValue_I=this.ngdInfoStructure[this.posSentence_I].getCoverage();
				coverageValue_II=this.ngdInfoStructure[this.posSentence_II].getCoverage();
				redundancyValue=calculateRedundancy(this.posSentence_I, this.posSentence_II);
				fitnessFunctValue=fitnessFunctValue+(coverageValue_I+coverageValue_II-redundancyValue);
				//
				this.posSentence_I=this.posSentence_II;
			}
		}
		return fitnessFunctValue;
	}
	/**
	 * Return the total length of the sentences selected in the individual.  
	 */
	public int calculateTotalLengthInd(int [] individual){
		int length=0;
		for(this.indPos=0;this.indPos<individual.length;this.indPos++){
			if(individual[this.indPos]==1){
				length=length+this.ngdInfoStructure[this.indPos].getLength();
			}
		}
		return length;
	}
	/**
	 * Calculates the similarity between two terms using NGD formula.
	 * Also replace the value in the pairsTermMap for the NGD value.
	 */
	private float termSimNGD=0;
	private double ngd_value=0,aux_double_fk=0,aux_double_fl=0,aux_double_flk=0;
	private void calculateNGDTerms(String term_I,String term_II){
		if(term_I.compareTo(term_II)<0){
			//term_I comes alphabetically before than..
			this.aux_double_fk=this.termMap.get(term_I);
			this.aux_double_fl=this.termMap.get(term_II);
			if(this.pairsTermMap.get(term_I+","+term_II)!=null){
				this.aux_double_flk=this.pairsTermMap.get(term_I+","+term_II);
				
				
				/*
				BUG ANOTATION:
					SOMETIMES BY UNKOWN REASON THE TERM FREQUENCY IS EQUALS TO 0 EN IT HAS NOT TO HAPPEN
					
					SO A TEMPORARY SOLUTION IS TO AVOID LOG(0)
				*/
				
				if(!(this.aux_double_fk>0))
					this.aux_double_fk=1;
				if(!(this.aux_double_fl>0))
					this.aux_double_fl=1;
				
				/*
				END OF BUG SOLUTION
				
				*/
				
				//Calculate NGD
				this.ngd_value=(Math.max(Math.log10(this.aux_double_fk), Math.log10(this.aux_double_fl))-Math.log10(this.aux_double_flk))
						/
						(Math.log10(this.totalNumSent)-Math.min(Math.log10(this.aux_double_fk), Math.log10(this.aux_double_fl)));
					
				
				//
				//Calculate sim_term_NGD(t_k, t_l)
				termSimNGD=(float)Math.exp(-this.ngd_value);
				//
				//Replace the value in the pairsTermMap
				this.pairsTermMap.put(term_I+","+term_II,termSimNGD);
			}
		}else{
			this.aux_double_fk=this.termMap.get(term_I);
			this.aux_double_fl=this.termMap.get(term_II);
			if(this.pairsTermMap.get(term_II+","+term_I)!=null){
				this.aux_double_flk=this.pairsTermMap.get(term_II+","+term_I);
				//Calculate NGD
				this.ngd_value=(Math.max(Math.log10(this.aux_double_fk), Math.log10(this.aux_double_fl))-Math.log10(this.aux_double_flk))
						/
						(Math.log10(this.totalNumSent)-Math.min(Math.log10(this.aux_double_fk), Math.log10(this.aux_double_fl)));
				//
				//Calculate sim_term_NGD(t_k, t_l)
				termSimNGD=(float)Math.exp(-this.ngd_value);
				//
				//Replace the value in the pairsTermMap
				this.pairsTermMap.put(term_I+","+term_II,termSimNGD);
			}
			
		}
	}
	/**
	 * This function calculates the similarity between two sentences.
	 * The results of the similarity is stored in the CLASS variable:
	 * 	- similarityResult
	 * @param int sentPos_I The number id of the first sentence
	 * @param int sentPos_II The number id of the second sentence
	 */
	private String pairsMap_key="";
	private void calculateSimilarity(int sentPos_I,int sentPos_II){
		this.similarityResult=0;
		this.numerator=0;
		this.denominator=0;
		//
		//Define size of the array
		this.termText_Sent_I=new String[this.ngdInfoStructure[sentPos_I].getTerms().length];
		//Load the information in the array
		this.termText_Sent_I=this.ngdInfoStructure[sentPos_I].getTerms();
		
		//_____
		//Define size of the array
		this.termText_Sent_II=new String[this.ngdInfoStructure[sentPos_II].getTerms().length];
		//Load the information in the array
		this.termText_Sent_II=this.ngdInfoStructure[sentPos_II].getTerms();
		//______________________________
		//Calculating the Numerator
		this.numerator=0;
		for(this.cs_i=0;this.cs_i<this.termText_Sent_I.length;this.cs_i++){
			for(this.cs_j=0;this.cs_j<this.termText_Sent_II.length;this.cs_j++){
				if(this.termText_Sent_I[this.cs_i].compareTo(this.termText_Sent_II[this.cs_j])<0){
					//this.termText_Sent_I[this.cs_i] comes alphabetically before than..
					if(!this.termText_Sent_I[this.cs_i].equals(this.termText_Sent_II[this.cs_j])){
						//Check if the value exists
						this.pairsMap_key=this.termText_Sent_I[this.cs_i]+","+this.termText_Sent_II[this.cs_j];
						if(this.pairsTermMap.get(this.pairsMap_key)!=null){
							this.numerator=this.numerator+(this.pairsTermMap.get(this.pairsMap_key));
						}else{
							//check the other combination
							this.pairsMap_key=this.termText_Sent_II[this.cs_j]+","+this.termText_Sent_I[this.cs_i];
							if(this.pairsTermMap.get(this.pairsMap_key)!=null){
								this.numerator=this.numerator+(this.pairsTermMap.get(this.pairsMap_key));
							}else{
								this.numerator=this.numerator+(0);
							}
							
						}
					}
				}else{
					//this.termText_Sent_II[this.cs_j] comes alphabetically before than..
					this.pairsMap_key=this.termText_Sent_II[this.cs_j]+","+this.termText_Sent_I[this.cs_i];
					if(this.pairsTermMap.get(this.pairsMap_key)!=null){
						this.numerator=this.numerator+(this.pairsTermMap.get(this.pairsMap_key));
					}else{
						//check the other combination
						this.pairsMap_key=this.termText_Sent_I[this.cs_i]+","+this.termText_Sent_II[this.cs_j];
						if(this.pairsTermMap.get(this.pairsMap_key)!=null){
							this.numerator=this.numerator+(this.pairsTermMap.get(this.pairsMap_key));
						}else{
							this.numerator=this.numerator+(0);
						}
					}					
				}
				
			}
		}
		//Calculating the Denominator
		this.denominator=this.ngdInfoStructure[sentPos_I].getSentenceNorm()*this.ngdInfoStructure[sentPos_II].getSentenceNorm();
		//_______
		if(this.denominator>0)
			this.similarityResult=this.numerator/this.denominator;
		else
			System.err.println("Error: Division by 0. \nLine 333 \nClass NGDFitnessFunction"+
					this.ngdInfoStructure[sentPos_I].getSentenceNorm()+"\n"+this.ngdInfoStructure[sentPos_II].getSentenceNorm());
	}
	/**
	 * Calculates the coverage value for a given sentence.
	 */
	private double calculateCoverage(int sentencePosition){
		double coverageValue=0;
		for(int w=0;w<this.ngdInfoStructure.length;w++){
			calculateSimilarity(sentencePosition, w);
			coverageValue=coverageValue+this.similarityResult;
		}
		return coverageValue;
	}
	/**
	 * Calculates the redundancy value for a given pair of sentences.
	 */
	private double calculateRedundancy(int sentPos_I,int sentPos_II){
		double redundancy=0;
		calculateSimilarity(sentPos_I, sentPos_II);
		redundancy=this.similarityResult;
		return redundancy;
	}
	public int getMaxExecLength() {
		return maxExecLength;
	}
	public void setMaxExecLength(int maxExecLength) {
		this.maxExecLength = maxExecLength;
	}
	public int getMaxLength() {
		return maxLength;
	}
	public void setMaxLength(int maxLength) {
		this.maxLength = maxLength;
	}
	

	/**
	 * This method receives a ByteBuffer and returns the String that it represents.
	 * @param ByteBuffer ByteBuffer to be transformed.
	 * @return String String representation of the ByteBuffer. 
	 */
	private static Charset charset = Charset.forName("ISO8859_1");
	private static CharsetEncoder encoder = charset.newEncoder();
	private static CharsetDecoder decoder = charset.newDecoder();
	public String BBuffertoString(ByteBuffer buffer){
		String data = "";
		  try{
		    int old_position = buffer.position();
		    data = decoder.decode(buffer).toString();
		    // reset buffer's position to its original so it is not altered:
		    buffer.position(old_position);  
		  }catch (Exception e){
		    e.printStackTrace();
		    return "";
		  }
		  return data;
	}
	/**
	 * This method receives a String returns the ByteBuffer that represents that String.
	 * @param String String to be transformed.
	 * @return ByteByffer ByteBuffer representation of the String. 
	 */
	public ByteBuffer stringToBBuffer(String msg){
		try{
		    return encoder.encode(CharBuffer.wrap(msg));
		  }catch(Exception e){e.printStackTrace();}
		  return null;
	}
	/**
	 * @throws IOException
	 */
	private String getValidInformationFromBuff(FileChannel f,int inBufCap,ByteBuffer inBuf, ByteBuffer auxBuf,float fsize) throws IOException{
		String validStringInfo="";
		//The variables below are utilitary variables which helps us to do the needed tasks
        char[] charArr;
        String s;
        long len = 0;
        int i=0;
        float i_filePosition=0,f_filePosition=0;
        while((len = f.read(inBuf)) != -1) {
    		inBuf.flip();
            //Profiling code lines
            s=BBuffertoString(inBuf);
            /**
             * Because the use of a fixed sized buffer it is possible to read incomplete instances, for example:
             * "First buffer read"
             * 		Instance_1  Field_1 Field_2 Part(Field_3)
             * "Second buffer read"
             * 		left_part(instance_1,Field_3)
             * 		Instance_2  Field_1 Field_2 Field_3
             * 
             * So the lines written below pretend to analyze the information in the buffer an only take the information where
             * the instances are complete. After the valid information was selected the position of the "filechannel" is reallocated
             * in order to start from the last valid end point.
             */
            //Transform the String information in the buffer, in a character array
            charArr=s.toCharArray();
            //Read the char array backwards
            for(i=charArr.length-1;i>-1;i--){
            	//Look for the last valid instance and used the other condition to avoid an infinite loop due to the reallocation
            	if(charArr[i]=='\n'&&(fsize-f.position())>=inBufCap){
            		//Extract the valid information
            		auxBuf=stringToBBuffer(s.substring(0, i));
            		//Calculate the new last valid position in the "filechannel"
            		f_filePosition=i_filePosition+auxBuf.limit();
            		//Reallocate
            		f.position((long)f_filePosition);
            		break;
            	}
            }
            //Clean the auxiliary variables
            charArr=null;
            //If it is the last chunk just write it, if it is analyzed as it was done previously you get a loop because the reallocation
            if((fsize-f.position())<inBufCap){
            	//Clean the auxiliary variables
            	auxBuf.clear();
            	//
            	validStringInfo=BBuffertoString(inBuf);
            }else{
            	//Clean the auxiliary variables
            	inBuf.clear();
            	//
            	validStringInfo=BBuffertoString(auxBuf);
            }
            //******
            inBuf.clear();
            //The line bellow stores the final valid position, where the file was read. 
            i_filePosition=f.position();
        }
		return validStringInfo;
	}
}
